﻿using System;
using System.IO;
using System.ComponentModel;
using System.Web;
using System.Net;
using System.Xml;
using System.Xml.Serialization;
using System.Windows.Forms;
using System.Resources;

// TODO: WebupdateItem and WebupdateDocument classes don't need to be public,
//       but the serialization code only works when they are public?!
//       The dependency to VsTortoiseSVN.ResourceManager sucks

namespace VsTortoiseSVN
{
    /// <summary>
    /// Represents information about an available version,
    /// downloaded from an XML document from a server.
    /// </summary>
    [Serializable]
    public sealed class WebupdateItem
    {
        /// <summary>
        /// Build number of the available update or -1 when none is available.
        /// </summary>
        [XmlElement("Build")]
        public int Build { get; set; }

        /// <summary>
        /// URL where to obtain the update from or string.Empty when none is available.
        /// </summary>
        [XmlElement("URL")]
        public string URL { get; set; }

        public WebupdateItem()
        {
            Reset();
        }

        public void Reset()
        {
            Build = -1;
            URL = string.Empty;
        }
    }

    /// <summary>
    /// Represents the data downloaded from an XML
    /// document from a server.
    /// </summary>
    [Serializable]
    public sealed class WebupdateDocument
    {
        /// <summary>
        /// Update information about a stable release
        /// </summary>
        [XmlElement("Stable")]
        public WebupdateItem Stable { get; set; }

        /// <summary>
        /// Update information about a beta release
        /// </summary>
        [XmlElement("Beta")]
        public WebupdateItem Beta { get; set; }

        public WebupdateDocument()
        {
            Stable = new WebupdateItem();
            Beta = new WebupdateItem();
        }

        public void Reset()
        {
            Stable.Reset();
            Beta.Reset();
        }
    }

    /// <summary>
    /// Webupdate is a helper class to download a file from a server with
    /// information about the latest VsTortoise version. It requests an XML
    /// file from a server, downloads the content and creates a WebupdateDocument
    /// object using the downloaded information. The server request etc runs in another thread.
    /// </summary>
    public sealed class Webupdate
    {
        #region Private Variables
        private BackgroundWorker _worker;
        private bool _involkedByUser;
        private WebupdateDocument _document;
        #endregion

        #region Private Constants
        /// <summary>
        /// Gets the URL where to retrieve version information from.
        /// </summary>
        private const string LatestVersionXmlURL = "http://www.console-dev.de/vstortoise/latestversion.xml";
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets whether the updater thread is busy.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                return _worker.IsBusy;
            }
        }

        /// <summary>
        /// Gets whether an error occured during web-requests.
        /// </summary>
        public bool HasError { get; private set; }

        /// <summary>
        /// Gets/sets whether to consider beta builds when checking for updates.
        /// </summary>
        public bool ConsiderBeta { get; set; }

        /// <summary>
        /// Gets whether an update is available.
        /// </summary>
        public bool IsAvailable
        {
            get
            {
                string url = URL;
                return !string.IsNullOrEmpty(url);
            }
        }

        /// <summary>
        /// Gets the URL where to download the new build from or
        /// an empty string when no new build is available.
        /// </summary>
        public string URL
        {
            get
            {
                // When an error occured, an update cannot be available
                if (HasError)
                    return string.Empty;

                // Check if user is interested in beta builds. Since beta builds are
                // usually always newer than stable builds, this is handled first.
                if (ConsiderBeta && _document.Beta.Build > Globals.Build)
                    return _document.Beta.URL;

                // A newer stable build is available
                if (_document.Stable.Build > Globals.Build)
                    return _document.Stable.URL;

                // We run an alpha or beta build and a new stable version
                // with at least the same build number is available.
                if ((Globals.Alpha || Globals.Beta) && _document.Stable.Build >= Globals.Build)
                    return _document.Stable.URL;

                // We run an alpha build and a new beta with at least
                // the same build number is available.
                if(Globals.Alpha && _document.Beta.Build >= Globals.Build)
                    return _document.Beta.URL;

                return string.Empty;
            }
        }

        /// <summary>
        /// Event when the update has been completed.
        /// </summary>
        public event RunWorkerCompletedEventHandler Completed
        {
            add { _worker.RunWorkerCompleted += value; }
            remove { _worker.RunWorkerCompleted -= value; }
        }
        #endregion

        #region Singleton
        private static Webupdate _instance;
        public static Webupdate Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new Webupdate();

                return _instance;
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        private Webupdate()
        {
            _worker = new BackgroundWorker();
            _worker.DoWork += new DoWorkEventHandler(WorkMethod);
            _worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(CompletedMethod);
            _worker.WorkerSupportsCancellation = true;
            _document = new WebupdateDocument();

            //DumpXML();
        }
        #endregion

        #region Methods
        [System.Diagnostics.Conditional("DEBUG")]
        private void DumpXML()
        {
            System.Xml.XmlDocument doc = new XmlDocument();

            WebupdateDocument obj = new WebupdateDocument();
            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());

            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            serializer.Serialize(stream, obj);
            stream.Position = 0;
            doc.Load(stream);
            doc.Save("latestversion.xml");
        }

        /// <summary>
        /// Aborts the current update thread.
        /// </summary>
        public void Abort()
        {
            if (_worker.IsBusy)
                _worker.CancelAsync();
        }

        /// <summary>
        /// Starts the update thread if none is running.
        /// </summary>
        public void Start(bool byUser)
        {
            Output.Instance.WriteLine(string.Format("Starting webupdate at {0} (InvokedByUser={1})", DateTime.Now, byUser));

            if (IsBusy)
                return;

            _involkedByUser = byUser;
            _worker.RunWorkerAsync();
        }

        /// <summary>
        /// Clears the Webupdate so it pretents no build is available.
        /// </summary>
        private void Clear()
        {
            HasError = false;

            // Create a new document, because when deserialization failed,
            // it is likely _document is null.
            _document = new WebupdateDocument();
        }

        /// <summary>
        /// BackgroundWorker method when the threaded Webupdate begins.
        /// </summary>
        private void WorkMethod(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            try
            {
                Clear();
                CheckForUpdate();
            }
            catch
            {
                Clear();
                HasError = true;
            }
        }

        /// <summary>
        /// Here actually happens the server request.
        /// </summary>
        private void CheckForUpdate()
        {
            HttpWebRequest request = HttpWebRequest.CreateDefault(new Uri(LatestVersionXmlURL)) as HttpWebRequest;
            request.Method = "GET";
            request.UserAgent = "VsTortoise";

            WebResponse response = request.GetResponse();

            XmlReader reader = XmlTextReader.Create(response.GetResponseStream());
            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(_document.GetType());
            _document = serializer.Deserialize(reader) as WebupdateDocument;
        }

        /// <summary>
        /// BackgroundWorker method when the threaded Webupdate completed.
        /// </summary>
        private void CompletedMethod(object sender, RunWorkerCompletedEventArgs e)
        {
            Output.Instance.WriteLine(string.Format("Webupdate completed at {0} (InvokedByUser={1}, IsAvailable={2}, HasError={3})", DateTime.Now, _involkedByUser, IsAvailable, HasError));

            if (IsAvailable)
            {
                // A new version is available, always report this to the user,
                // no matter if it is auto webupdate or invoked by the user.
                string text = string.Format(SatelliteDLL.Instance.GetText("WebupdateAvailable"), URL);
                if (System.Windows.Forms.MessageBox.Show(text, Globals.AppTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    Win32.ShellExecute(URL, "");
            }
            else if (_involkedByUser && HasError)
            {
                // Only display an error message when the user actually invoked webupdate.
                // Don't displays error messages when the auto webupdate just failed.
                string text = string.Format(SatelliteDLL.Instance.GetText("WebupdateError"));
                if (System.Windows.Forms.MessageBox.Show(text, Globals.AppTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Error) == DialogResult.Yes)
                    Win32.ShellExecute("http://vstortoise.codeplex.com", "");
            }
            else if (_involkedByUser)
            {
                // Only display a message when the user actually invoked webupdate.
                string text = string.Format(SatelliteDLL.Instance.GetText("WebupdateNone"));
                System.Windows.Forms.MessageBox.Show(text, Globals.AppTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        #endregion
    }
}
